package com.moralesdelcastillo.guestbook.client;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FormPanel;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.RadioButton;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.FormPanel.SubmitCompleteEvent;
import com.google.gwt.user.client.ui.FormPanel.SubmitEvent;

import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;

import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.http.client.URL;


/**
 * Entry point classes define <code>onModuleLoad()</code>.
 */
public class GuestBook implements EntryPoint {

	private final FormPanel form = new FormPanel();
	private Grid layoutForm = new Grid(8,4); //filas, columnas. He tenido que añadir una más de las que necesito para que funcione
	private TextBox nameTextBox = new TextBox();
	private TextBox lastNameTextBox = new TextBox();
	private TextArea commentTextArea = new TextArea();
	private RadioButton maleRB = new RadioButton("gender", "H");
	private RadioButton femaleRB = new RadioButton("gender", "M");
	private Button addGuestButton = new Button("Añadir");
	private HorizontalPanel genderPanel = new HorizontalPanel();
	private ListBox country = new ListBox();
	private TextBox emailTextBox = new TextBox();
	private Label nameErrorLabel = new Label();
	private Label lastNameErrorLabel = new Label();
	private Label genderErrorLabel = new Label();
	private Label commentErrorLabel = new Label();
	private Label emailErrorLabel = new Label();
	private enum ValidationType {STRING, CHOICE, EMAIL};
	
	private VerticalPanel layoutList = new VerticalPanel();
	
	private VerticalPanel layoutMessage = new VerticalPanel();
	private Label thanksMessage = new Label();
	
	private static final String INTEGER_URL = "http://localhost/guestbook/numberGuestsInDB.php";
	private static final String DATA_URL = "http://localhost/guestbook/getData.php";
	  
	//Create an arrayList to store invalids fields
	ArrayList<Widget> invalids = new ArrayList<Widget>();
	
	//private FlexTable guestsFlexTable = new FlexTable();
	private Map<Widget, HashMap> itemsInForm = new HashMap<Widget, HashMap>();

	
	
	/**
	 * Entry point method.
	 */

	public void onModuleLoad() {
		form.setAction("http://localhost/guestbook/add.php");
		form.setMethod(FormPanel.METHOD_POST);
		form.setWidget(layoutForm);
		form.setStyleName("form-background");
		
		addGuestForm();
		addGuestsList();
		
		showCommentsList();
		
	    // Add elements to hashmap items
	    initializeItemsInForm();
	    initializeMessageDiv();
	    
	    // Move cursor focus to the input box.
	    nameTextBox.setFocus(true);
	    
	    // Listen for mouse events on the Add button.
	    addGuestButton.addClickHandler(new ClickHandler() {
	      public void onClick(ClickEvent event) {
	        form.submit();
	      }
	    });
	    
	    // TODO: hacer esto para todos los widgets
		nameTextBox.addKeyUpHandler(new KeyUpHandler() {
			@Override
			public void onKeyUp(KeyUpEvent event) {
				if (!validateString(nameTextBox.getText())){
					nameErrorLabel.setVisible(true);
				}
				else{
					nameErrorLabel.setVisible(false);
				}
				
			}
		});
			
		lastNameTextBox.addKeyUpHandler(new KeyUpHandler() {
			@Override
			public void onKeyUp(KeyUpEvent event) {
				if (!validateString(nameTextBox.getText())){
					lastNameErrorLabel.setVisible(true);
				}
				else{
					lastNameErrorLabel.setVisible(false);
				}
				
			}
		});
	    
		emailTextBox.addKeyUpHandler(new KeyUpHandler() {
			@Override
			public void onKeyUp(KeyUpEvent event) {
				if (!validateEmail(emailTextBox.getText())){
					emailErrorLabel.setVisible(true);
				}
				else{
					emailErrorLabel.setVisible(false);
				}
				
			}
		});
		
		
		
	    // Add an event handler to the form.
	    form.addSubmitHandler(new FormPanel.SubmitHandler() {
	      public void onSubmit(SubmitEvent event) {
	        // This event is fired just before the form is submitted. We can take
	        // this opportunity to perform validation.
	    	setWidgetsEnable("disabled");
	    	if (!validateForm()){
	    		event.cancel();
	    		thanksMessage.setText("Ops! Revisa los datos introducidos en el formulario, parece que hay algún error en ellos");
	    	}
	      }
	    });
	    
	    form.addSubmitCompleteHandler(new FormPanel.SubmitCompleteHandler() {
	      public void onSubmitComplete(SubmitCompleteEvent event) {
	        // When the form submission is successfully completed, this event is
	        // fired. Assuming the service returned a response of type text/html,
	        // we can get the result text here (see the FormPanel documentation for
	        // further explanation).
	        setWidgetsEnable("enabled");
	        cleanWidgets();
	        showCommentsList();
	        thanksMessage.setVisible(true);
	      }
	    });
	}
	
	private void initializeMessageDiv() {
		thanksMessage.setText("Tu comentario ha sido añadido. Gracias por participar.");
		layoutMessage.add(thanksMessage);
		thanksMessage.setVisible(false);
		RootPanel.get("message").add(layoutMessage);
	}
	
	private void updateTable(JsArray<GuestData> guests) {
		layoutList.clear();
	    for (int i = 0; i < guests.length(); i++) {
	      updateTable(guests.get(i));
	    }    
	}
	
	private void updateTable(GuestData guest) {
	    layoutList.add(new Label(guest.getName() + " " + guest.getLastName()));
	    layoutList.add(new Label(guest.getComment()));
	    
	}

	private void cleanWidgets(){
		//Clean the text of the widgets
		Set set = itemsInForm.entrySet();
		Iterator iter = set.iterator();
		Widget key = null;
		HashMap value = null;
		
	    while (iter.hasNext()) {
	      Map.Entry entry = (Map.Entry) iter.next();
	      key = (Widget)entry.getKey();
	      value = (HashMap)entry.getValue();
	      
	      //Evaluate type of validation it needs
	      switch ((ValidationType)value.get("validation")) {
	      	case STRING:
	      	case EMAIL:
	      		if (key instanceof TextBox){
					((TextBox)key).setText("");
				}
				else if(key instanceof TextArea){
					((TextArea)key).setText("");
				}
	      		break;
	      	case CHOICE:
	      		((RadioButton)key).setValue(false);
	      		break;
	      	default: assert false;
	      }
	    }
	}
	
	private void setWidgetsEnable(String s){
		//TODO: comprobar que s sólo puede valer 'disabled' o 'enabled'
		Set set = itemsInForm.entrySet();
		Iterator iter = set.iterator();
		Widget key = null;
		HashMap value = null;
		
	    while (iter.hasNext()) {
	      Map.Entry entry = (Map.Entry) iter.next();
	      key = (Widget)entry.getKey();
	      //value = (HashMap)entry.getValue();
	      //key.setVisible(b);
	      key.addStyleName(s);
	    }
	}
	
	private boolean validateString (String cadena) {
		//Name code must be between A and Z chars or a white space
	    return cadena.trim().matches("^[a-zA-Z\\sáéíóú]{1,100}$");
	}
	
	private boolean validateForm(){
		invalids.clear();
		
		String groupName = null;
		HashMap<String, Boolean> groups = new HashMap<String, Boolean>();
		
		//Iterate through itemsInForm validating the fields
		Set set = itemsInForm.entrySet();
		Iterator iter = set.iterator();
		Widget key = null;
		HashMap value = null;
		
	    while (iter.hasNext()) {
	      Map.Entry entry = (Map.Entry) iter.next();
	      key = (Widget)entry.getKey();
	      value = (HashMap)entry.getValue();
	      
	      //Evaluate type of validation it needs
	      switch ((ValidationType)value.get("validation")) {
	      	case STRING: 
	      		String cadena = null;
	      				
	      		if (key instanceof TextBox){
  					cadena = ((TextBox)key).getText();
  				}
  				else if(key instanceof TextArea){
  					cadena = ((TextArea)key).getText();
  				}
  				
  				if (!validateString(cadena)){
  					invalids.add(key);
      			}
  				
  				break;
	      		
	      	case CHOICE: 
	      		// For every widget check if there is any radio-button activated 
	      		groupName = (String)value.get("group");
	      		
	      		assert (groupName != null) : "Group name is null";
	      		
	      		if (!groups.containsKey(groupName)){
	      			groups.put(groupName, false);
	      		}
	      		
	      		// If RadioButton is activated, set groups to true for this groupName
	      		if (((RadioButton)key).getValue()){
	      			groups.remove(groupName);
	      			groups.put(groupName, true);
	      		}
	      		break;
	      	case EMAIL: 
	      		if (((TextBox)key).getText() != "") {
	      			
	      			try {
	      				boolean b = validateEmail(((TextBox)key).getText());
	      				
	      				if (!b){
		  					invalids.add(key);
		      			}
	      			}
	      			catch (Exception e){
	      				debug("Error al validar el e-mail");
	      			}
	      			
	      			
	      		}
	      		break;
	      	default: assert false;
	      }
	    }
	    
	    // Add to invalids groups without any checked widget
	    Set setGroups = groups.entrySet();
		Iterator iterGroups = setGroups.iterator();
		String keyG = null;
		Boolean valueG = null;
		Widget w = null;
		
	    while (iterGroups.hasNext()) {
	      Map.Entry entryG = (Map.Entry) iterGroups.next();
	      keyG = (String)entryG.getKey();
	      valueG = (Boolean)entryG.getValue();
	      
	      // Add a widget of the group to invalids
	      if (!valueG){
	    	  w = getRadioButtonFromGroup(keyG);
	    	  invalids.add(w);
	      }
	    }
	    
	    deleteAllErrorLabels();
	    showErrorLabels(invalids);
	    return (invalids.isEmpty()); 
	}
	
	private boolean validateEmail(String cadena) {
		return cadena.trim().matches("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-_]+\\.[A-Za-z]{2,}$");
            
	}
	
	private Widget getRadioButtonFromGroup(String groupName){
		Set set = itemsInForm.entrySet();
		Iterator iter = set.iterator();
		Widget key = null;
		HashMap<String, Object> value = null;
		
	    while (iter.hasNext()) {
	      Map.Entry entry = (Map.Entry) iter.next();
	      key = (Widget)entry.getKey();
	      value = (HashMap<String, Object>)entry.getValue();
	      
	      if (value.containsValue(groupName)){
	    	  return key;
	      }
	    }
	    return null;
	}
	
	private void deleteAllErrorLabels(){
		Set set = itemsInForm.entrySet();
		Iterator iter = set.iterator();
		Widget key = null;
		HashMap<String, Object> value = null;
		Label l = null;
		
	    while (iter.hasNext()) {
	      Map.Entry<Widget, HashMap<String, Object>> entry = (Map.Entry<Widget, HashMap<String, Object>>) iter.next();
	      key = (Widget)entry.getKey();
	      value = (HashMap<String, Object>)entry.getValue();
	      
	      //Get errorLabel of this widget
	      l = (Label)value.get("errorLabel");
	      l.setVisible(false);
	    }
	    
	}
	
	private void showErrorLabels(ArrayList inv){
		Iterator iter = inv.listIterator();
		
	    // For every item in invalids, show its labelError
		Label l = null;
		while(iter.hasNext()) {
	          Widget w = (Widget)iter.next(); 
	          // Find its errorLabel and show it
	          l = (Label)itemsInForm.get(w).get("errorLabel");
	          l.setVisible(true);
		}
	}
	
	
	private void initializeItemsInForm(){
		HashMap<String, Object> hmName = new HashMap<String, Object>();
	    hmName.put("valor", null);
	    hmName.put("errorLabel", nameErrorLabel);
	    hmName.put("validation", ValidationType.STRING);
	    hmName.put("group", null);
	    itemsInForm.put(nameTextBox, hmName);
	    
	    HashMap<String, Object> hmLastName = new HashMap<String, Object>();
	    hmLastName.put("valor", null);
	    hmLastName.put("errorLabel", lastNameErrorLabel);
	    hmLastName.put("validation", ValidationType.STRING);
	    hmLastName.put("group", null);
	    itemsInForm.put(lastNameTextBox, hmLastName);
	    
	    HashMap<String, Object> hmComment = new HashMap<String, Object>();
	    hmComment.put("valor", null);
	    hmComment.put("errorLabel", commentErrorLabel);
	    hmComment.put("validation", ValidationType.STRING);
	    hmComment.put("group", null);
	    itemsInForm.put(commentTextArea, hmComment);
	    
	    HashMap<String, Object> hmMale = new HashMap<String, Object>();
	    hmMale.put("valor", null);
	    hmMale.put("errorLabel", genderErrorLabel);
	    hmMale.put("validation", ValidationType.CHOICE);
	    hmMale.put("group", "gender");
	    itemsInForm.put(maleRB, hmMale);
	    
	    HashMap<String, Object> hmFemale = new HashMap<String, Object>();
	    hmFemale.put("valor", null);
	    hmFemale.put("errorLabel", genderErrorLabel);
	    hmFemale.put("validation", ValidationType.CHOICE);
	    hmFemale.put("group", "gender");
	    itemsInForm.put(femaleRB, hmFemale);
	    
	    HashMap<String, Object> hmEmail = new HashMap<String, Object>();
	    hmEmail.put("valor", null);
	    hmEmail.put("errorLabel", emailErrorLabel);
	    hmEmail.put("validation", ValidationType.EMAIL);
	    hmEmail.put("group", null);
	    itemsInForm.put(emailTextBox, hmEmail);
	}
	
	private void addGuestForm(){
		//Añadimos controles a layoutForm
		layoutForm.setWidget(1, 1, new Label("Nombre: "));
		layoutForm.setWidget(1, 2, nameTextBox);
		layoutForm.setWidget(1, 3, nameErrorLabel);
		layoutForm.setWidget(2, 1, new Label("Apellidos "));
		layoutForm.setWidget(2, 2, lastNameTextBox);
		layoutForm.setWidget(2, 3, lastNameErrorLabel);
		layoutForm.setWidget(3, 1, new Label("E-mail: "));
		layoutForm.setWidget(3, 2, emailTextBox);
		layoutForm.setWidget(3, 3, emailErrorLabel);
		layoutForm.setWidget(4, 1, new Label("Sexo: "));
		layoutForm.setWidget(4, 2, genderPanel);
		layoutForm.setWidget(4, 3, genderErrorLabel);
		layoutForm.setWidget(5, 1, new Label("País: "));
		layoutForm.setWidget(5, 2, country);
		layoutForm.setWidget(6, 1, new Label("Comentario"));
		layoutForm.setWidget(6, 2, commentTextArea);
		layoutForm.setWidget(6, 3, commentErrorLabel);
		layoutForm.setWidget(7, 2, addGuestButton);
		
		//Indicamos el nombre de cada widget y establecemos tamaño máximo de la entrada		nameTextBox.setName("name");
		nameTextBox.setName("name");
		nameTextBox.setMaxLength(100);
		lastNameTextBox.setName("lastName");
		lastNameTextBox.setMaxLength(200);
		emailTextBox.setName("email");
		emailTextBox.setMaxLength(100);
		commentTextArea.setName("comment");
		commentTextArea.setCharacterWidth(30);
		commentTextArea.setVisibleLines(5);
		country.setName("country");
		
		//Set value for radiobuttons
		maleRB.setFormValue("M");
		femaleRB.setFormValue("F");
		
		//Add elements to sexPanel
		genderPanel.add(maleRB);
		genderPanel.add(femaleRB);
		
		//Add elements to country
		country.addItem("España", "ES");
		country.addItem("Italia", "IT");
		country.addItem("Portugal", "PT");
		
		//Add text to label error
		nameErrorLabel.setText("El nombre indicado contiene caracteres no válidos");
		nameErrorLabel.setVisible(false);
		nameErrorLabel.setStyleName("error");
		lastNameErrorLabel.setText("El apellido indicado contiene caracteres no válidos");
		lastNameErrorLabel.setVisible(false);
		lastNameErrorLabel.setStyleName("error");
		emailErrorLabel.setText("El correo electrónico indicado parece incorrecto");
		emailErrorLabel.setVisible(false);
		emailErrorLabel.setStyleName("error");
		genderErrorLabel.setText("Debes elegir un género");
		genderErrorLabel.setVisible(false);
		genderErrorLabel.setStyleName("error");
		commentErrorLabel.setText("Debes escribir un comentario utilizando caracteres válidos");
		commentErrorLabel.setVisible(false);
		commentErrorLabel.setStyleName("error");
		
		// Associate the Main panel with the HTML host page.
	    RootPanel.get("guestForm").add(form);
	}

	private void showCommentsList() {
		// Check if there are guests or not, to show the list of guests
		// or a message of no-comments
		
		String url = URL.encode(INTEGER_URL);
		
		// Send request to server and catch any errors.
	    RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, url);

	    try {
	    	Request request = builder.sendRequest(null, new RequestCallback() {
	          public void onError(Request request, Throwable exception) {
	            //displayError("Couldn't retrieve JSON - 1");
	        	  Window.alert("Error al recuperar los datos");
	          }

	          public void onResponseReceived(Request request, Response response) {
	            if (200 == response.getStatusCode()) {
	            	// Check the number of comments in the database
	            	if (new Integer(response.getText()) <= 0){
	            		messageNoComments();
	            	}
	            	else{
	            		retrieveData();
	            	}
	            } 
	            else {
	              //displayError("Couldn't retrieve JSON - 2 (" + response.getStatusText()
	              //    + ")");
	            	Window.alert("Error al recuperar los datos");
	            }
	          }
	        });
	      } catch (RequestException e) {
	        //displayError("Couldn't retrieve JSON - 3");
	    	  debug("Error al recuperar los datos");
	        e.printStackTrace();
	      }
	}
	
	private void retrieveData() {
		String url = URL.encode(DATA_URL);
		
		// Send request to server and catch any errors.
	    RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, url);

	    try {
	    	Request request = builder.sendRequest(null, new RequestCallback() {
	          public void onError(Request request, Throwable exception) {
	            //displayError("Couldn't retrieve JSON - 1");
	        	  debug("Error al recuperar los datos");
	          }

	          public void onResponseReceived(Request request, Response response) {
	            if (200 == response.getStatusCode()) {
	            	updateTable(asArrayOfGuestData(response.getText()));
	            } 
	            else {
	              //displayError("Couldn't retrieve JSON - 2 (" + response.getStatusText()
	              //    + ")");
	            	debug("Error al recuperar los datos");
	            }
	          }
	        });
	      } catch (RequestException e) {
	        //displayError("Couldn't retrieve JSON - 3");
	    	  debug("Error al recuperar los datos");
	        e.printStackTrace();
	      }
	}

	
	private void addGuestsList(){
		//layoutList.add(Widget w);
		
		RootPanel.get("guestList").add(layoutList);
	}
	
	public final native void debug(String s) /*-{ if(window.console && window.console.firebug) {console.debug(s);} else {alert(s);} }-*/;
	
	private final native JsArray<GuestData> asArrayOfGuestData(String json) /*-{
    	return eval(json);
  	}-*/;
	
	private void messageNoComments(){
		layoutList.add(new Label("No hay comentarios"));
	}
}
